package com.gitee.apanlh.util.net.addr;

import com.gitee.apanlh.exp.IpAddrException;
import com.gitee.apanlh.util.base.CollUtils;
import com.gitee.apanlh.util.base.StringUtils;
import com.gitee.apanlh.util.dataformat.JsonUtils;
import com.gitee.apanlh.util.encode.CharsetCode;
import com.gitee.apanlh.util.net.http.HttpUtils;
import com.gitee.apanlh.util.reflection.ClassConvertUtils;
import com.gitee.apanlh.util.valid.ValidParam;
import jakarta.servlet.http.HttpServletRequest;

import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;

/**
 * 	获取IP地址
 * 	#mark 梳理重构
 *
 * 	@author Pan
 */
public class IpUtils {
	
	private static final int ZERO_ASCII = 48;
	/** IP标识符 */
	private static final char IP_TAG = '.';
	private static final String IP_HIDE_TAG = "***";
	/** 获取IP头 */
	private static final String[] DEFAULT_IP_HEADER = {"X-Real-IP", "X-Forwarded-For", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"};
	
	/**
	 * 	构造函数
	 * 	@author Pan
	 */
	private IpUtils() {
		//	不允许外部实例
		super();
	}
	
	
    /**	
     * 	IPV4
     * 	<br>根据前缀匹配到相对应的IP
     * 	<br>如果都为匹配则返回LocalIP 如果多网卡不适用
     * 	@author Pan
     * 	@param  preIp	前缀IP
     * 	@return	String
     */
    public static String prefixIpv4LocalAddress(String preIp) {
    	List<String> ipV4AllAddress = getIpV4AllAddress();
    	for (String ip : ipV4AllAddress) {
    		if (preIp.startsWith(ip)) {
    			return ip;
    		}
		}
		return getLocalAddress();
    }
    
    /**
     * 	获取IP地址
     * 	
     * 	@author Pan
     * 	@param  request	HttpServletRequest
     * 	@return	String
     */
    public static String getIpAddr(HttpServletRequest request) {
    	String ip = null;
    	String unknown = "unknown";
    	
    	for (int i = 0, len = DEFAULT_IP_HEADER.length; i < len; i++) {
			if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
				ip = request.getHeader(DEFAULT_IP_HEADER[i]);
			} else {
				break;
			}
		}
    	if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
    		ip = request.getRemoteAddr();
    	}
    	
    	if (ip.startsWith("0:0:0") || ip.startsWith("127.0.0.1")) {
    		ip = getLocalAddress();
    	}
        return ip;
    }
    
    /**
     * 	获取IP地址
     * 	
     * 	@author Pan
     * 	@param  request		HttpServletRequest
     * 	@return	String
     */
    public static String getIpAddrFirst(HttpServletRequest request) {
    	return getIpAddr(request).split(",")[0];
    }
    
    /**	
     * 	获取主机名以及ip地址
     * 	
     * 	@author Pan
     * 	@return	String	主机名/ip地址
     */
    public static String getLocalHost() {
    	String host;
    	try {
    		host = InetAddress.getLocalHost().toString();
		} catch (Exception e) {
			throw new IpAddrException(StringUtils.format("获取Host地址出现错误原因为:{}", e.getMessage(), e));
		}
    	return host;
    }
    
    /**	
     * 	获取主机名
     * 	
     * 	@author Pan
     * 	@return	String	主机名
     */
    public static String getLocalHostName() {
    	String host;
    	try {
    		host = InetAddress.getLocalHost().getHostName();
		} catch (Exception e) {
			throw new IpAddrException(StringUtils.format("获取主机名出现错误原因为:{}", e.getMessage(), e));
		}
    	return host;
    }

    /**	
     * 	获取本机IP地址
     * 	
     * 	@author Pan
     * 	@return	String	IP地址
     */
    public static String getLocalAddress() {
    	String address;
    	try {
    		address = InetAddress.getLocalHost().getHostAddress();
		} catch (Exception e) {
			throw new IpAddrException(StringUtils.format("获取本地ip地址出现错误原因为:{}", e.getMessage(), e));
		}
    	return address;
    }
    
    /**	
     * 	获取所有IP
     * 	<br>本机IP
     * 	<br>内网IP
     * 	<br>如果外网IP有配置则获取外网
     * 
     * 	@author Pan
     * 	@return	List
     */
    public static List<String> getIpV4AllAddress() {
    	try {
    		List<String> ipList = CollUtils.newArrayList();
			Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
			
			while (networkInterfaces.hasMoreElements()) {
				NetworkInterface networkInterface = networkInterfaces.nextElement();
				Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
				
				//	循环真实地址
				while (inetAddresses.hasMoreElements()) {
					InetAddress inetAddress = inetAddresses.nextElement();
					if (inetAddress == null) {
						continue;
					}
					//	只获取IPV4
					if (inetAddress instanceof Inet4Address) {
						ipList.add(inetAddress.getHostAddress());
					}
				}
			}
			return ipList;
		} catch (Exception e) {
			throw new IpAddrException(StringUtils.format("获取IPV4所有地址出现错误原因为:{}", e.getMessage(), e));
		}
    }
    
    /**	
     * 	获取所有网卡信息
     * 	
     * 	@author Pan
     * 	@return	String
     */
    public static String getNetInfo() {
    	try {
    		StringBuilder strBuilder = new StringBuilder();
			Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
			
			while (networkInterfaces.hasMoreElements()) {
				NetworkInterface networkInterface = networkInterfaces.nextElement();
				strBuilder.append(networkInterface.toString());
			}
			
			return strBuilder.toString();
		} catch (Exception e) {
			throw new IpAddrException(StringUtils.format("获取getNetInfo出现错误原因为:{}", e.getMessage(), e));
		}
    }
    
    /**	
     * 	获取 机器码信息
     * 	
     * 	@author Pan
     * 	@return	int
     */
    public static int getMachine() {
    	String netInfo = getNetInfo();
    	if (ValidParam.isNotNull(netInfo)) {
    		return netInfo.hashCode() << 16;
    	}
		return 0;
    }
    
    /**	
     * 	获取本机的外网IP地址
     * 	
     *	@author Pan
     * 	@return	String
     */
    public static String getOutIpv4() {
        String url = "https://ip.tool.chinaz.com/";
		try {
			String resultContent = HttpUtils.sendGet(url);
			String findFiled = "<dd class=\"fz24\">";
			
			int findIp = resultContent.indexOf(findFiled);
			if (findIp == -1) {
				return null;
			}
			
			return resultContent.substring(findIp + findFiled.length(), resultContent.indexOf("</dd>", findIp));
		} catch (Exception e) {
			throw new IpAddrException(StringUtils.format("获取外网IPV4地址错误:{}", e.getMessage(), e));
		}
    }
    
    /**	
     * 	当前本机公网IP包含地理信息
     * 	
     * 	@author Pan
     * 	@return	Map
     */
    public static Map<String, String> getIpAddr() {
    	return getIpAddr("local");
    }
    
    /**	
     * 	根据IP获取地址信息获取地理信息
     * 	
     * 	@author Pan
     * 	@param  ip	输入IP地址
     * 	@return	Map
     */
    public static Map<String, String> getIpAddr(String ip) {
    	String ipAddr;
    	String sendIpUrl = "https://whois.pconline.com.cn/ipJson.jsp?json=true&ip=";
    	if ("local".equals(ip)) {
    		ipAddr = HttpUtils.sendGet(sendIpUrl, CharsetCode.GBK);
    	} else {
    		ipAddr = HttpUtils.sendGet(sendIpUrl.concat(ip), CharsetCode.GBK);
    	}
    	return ipAddr != null ? ClassConvertUtils.castMapStr(JsonUtils.toMap(ipAddr)) : null;
    }
    
    /**	
     * 	IP转化为Long值
     * 	
     * 	@author Pan
     * 	@param  ip ip
     * 	@return	long
     */
    public static long toLongByIp(String ip) {
    	//	寻找下标
    	int findIndex = 0;
    	//	ipLong值
    	long[] ipValues = new long[4];
    	//	long数组下标
    	int longArrayIndex = 0;
    	
    	for (;;) {
    		//	非首次
    		if (findIndex != 0) {
    			findIndex ++;
    			//	数组填充
    			++longArrayIndex;
    		}
    		int ipIndexOf = ip.indexOf('.', findIndex);
    		
    		if (ipIndexOf == -1) {
    			//	最后一次第一个如果为数字
    			if (Character.isDigit(ip.charAt(findIndex))) {
    				ipValues[longArrayIndex] = Long.parseLong(ip.substring(findIndex, ip.length()));
    			}
    			break;
    		} else {
    			ipValues[longArrayIndex] = Long.parseLong(ip.substring(findIndex, ipIndexOf));
    		}
			findIndex = ipIndexOf;
    	}
    	
    	//IP加权计算
    	return checkIpRangeVal(ipValues) ? (ipValues[0] << 24) + (ipValues[1] << 16) + (ipValues[2] << 8) + ipValues[3] : -1L;
    }
    
    /**	
     * 	十进制IP转换标准格式IP
     * 	<br>1.右移24位
     * 	<br>2.将高8位置0，然后右移16位
     * 	<br>3.将高16位置0，然后右移8位
     * 	<br>4.将高24位置0
     * 	
     * 	@author Pan
     * 	@param  ip		Long类型IP地址
     * 	@return	String
     */
    public static String toIpByLongIp(Long ip) {
    	char tag = '.';
    	return new StringBuilder(16)
		  .append(ip >>> 24).append(tag)
		  .append((ip & 0x00FFFFFF) >>> 16).append(tag)
		  .append((ip & 0x00FFFFFF) >>> 8).append(tag)
		  .append(ip & 0x00FFFFFF)
	   .toString();
    }
    
    /**	
     * 	隐藏本地Ip
     * 	<br>默认隐藏后三位
     * 
     * 	@author Pan
     * 	@return	String
     */
    public static String hideLocalIp() {
    	return hideIp(getLocalAddress(), false, true);
    }
    
    /**	
     * 	隐藏本地Ip
     * 	<br>删除ip的.符号 少于3位的自动补足0
     * 
     * 	@author Pan
     * 	@return	String
     */
    public static String hideLocalIpByRemoveTag() {
    	return hideIp(getLocalAddress(), false, true);
    }
    
    /**	
     * 	隐藏公网IP
     * 	<br>默认隐藏后三位
     * 
     * 	@author Pan
     * 	@param  httpServletRequest HttpServletRequest
     * 	@return	String
     */
    public static String hideOutIp(HttpServletRequest httpServletRequest) {
    	return hideIp(getIpAddrFirst(httpServletRequest), true, false);
    }
    
    /**	
     * 	隐藏公网IP
     * 	<br>删除ip的.符号 少于3位的自动补足0
     * 
     * 	@author Pan
     * 	@param  httpServletRequest	HttpServletRequest
     * 	@return	String
     */
    public static String hideOutIpByRemoveTag(HttpServletRequest httpServletRequest) {
    	return hideIp(getIpAddrFirst(httpServletRequest), true, false);
    }
    
    /**	
     * 	隐藏IP方法
     * 	
     * 	@author Pan
     * 	@param  ip					ip地址
     * 	@param  removeTagAndZero	删除ip的.符号 少于3位的自动补足0;
     * 	@param  hideLast			是否隐藏最后
     * 	@return	String
     */
    private static String hideIp(String ip, boolean removeTagAndZero, boolean hideLast) {
    	StringBuilder ipBuilder = new StringBuilder(32);
    	
    	//	隐藏最后
    	if (hideLast) {
    		ipBuilder.append(ip.substring(0, ip.lastIndexOf(IP_TAG)))
    			     .append(IP_TAG)
					 .append(IP_HIDE_TAG);
    		return ipBuilder.toString();
    	}
    	
    	// 删除ip的.符号 少于3位的自动补足0
    	if (removeTagAndZero) {
	    	List<String> split = StringUtils.splitToList(ip, String.valueOf(IP_TAG));
	    	
			for (int i = 0, len = split.size(); i < len; i++) {
				String str = split.get(i);
				int needZero = 3 - str.length();
				for (int j = 0; j < needZero; j++) {
					//	不是3位则补足3位IP
					ipBuilder.append("0");
				}
				ipBuilder.append(str);
			}
    	}
		return ipBuilder.toString();
    }
    
    /**
     * 	校验保存IPV4是否合法性
     *
     * 	@author Pan
     * 	@param  ip		IPV4地址
     * 	@return	boolean
     */
    public static boolean isIpV4(String ip) {
        if (ValidParam.isEmpty(ip) || ip.charAt(0) == ZERO_ASCII || ip.length() > 15) {
            return false;
        }
        //	非数字验证
        String[] findNonNumber = StringUtils.findNonNumberToArray(ip);
        if (ValidParam.isEmpty(findNonNumber)) {
        	return false;
        }
        for (int i = 0; i < findNonNumber.length; i++) {
        	if (!".".equals(findNonNumber[i])) {
        		return false;
        	}
        }
        
        //	校验标识
        String[] splitIp = StringUtils.split(ip, ".", false);
        if (splitIp.length != 4) {
            return false;
        }

        //	校验IP
        for (int i = 0; i < splitIp.length; i++) {
            String ipStr = splitIp[i];
            char firstChar = ipStr.charAt(0);
            //	判断	网络地址与主机地址第四段验证
            if (ipStr.length() > 3 || ((i <= 1 || i == 3) && firstChar == ZERO_ASCII)) {
                return false;
            }
            //	判断	主机地址 判断第三段
            if (i == 2 && (ipStr.length() > 1 && firstChar == ZERO_ASCII)) {
                return false;
            }
        }
        return checkIpRangeVal(splitIp);
    }
    
    /**
     * 	校验是否超过范围值
     *
     * 	@author Pan
     * 	@param 	splitIp		分割段落的IP
     * 	@return	boolean
     */
    public static boolean checkIpRangeVal(String[] splitIp) {
        for (int i = 0, len = splitIp.length; i < len; i++) {
            String value = splitIp[i];
            if ((i == 0 && Integer.parseInt(value) > 223) || (i != 0 && Integer.parseInt(value) > 255)) {
                return false;
            }
        }
        return true;
    }
    
    /**
     * 	校验是否超过范围值
     *
     * 	@author Pan
     * 	@param 	ips		分割段落的IP
     * 	@return	boolean
     */
    public static boolean checkIpRangeVal(long[] ips) {
        for (int i = 0, len = ips.length; i < len; i++) {
            long value = ips[i];
            if ((i == 0 && value > 223) || (i != 0 && value > 255)) {
                return false;
            }
        }
        return true;
    }
}
